class Restaurant < ApplicationRecord
  LNG_LAT_INTERVAL = 0.000001

  has_many :dining_tables
  has_many :reservations
  has_many :work_times

  scope :enabled, -> { where status: 'enabled' }
  scope :filter_name, ->(name_in) { where 'name like ?', "%#{name_in}%" if name_in.present? }
  scope :filter_province, ->(province) { where province: province if province.present? }
  scope :filter_city, ->(city) { where city: city if city.present? }
  scope :filter_district, ->(district) { where district: district if district.present? }
  scope :filter_address, ->(address) { where '`address` like ?', "%#{address}%" if address.present? }
  scope :filter_gis, ->(lng, lat) {
    if lng.present? && lat.present?
      lng, lat = lng.to_f, lat.to_f
      where(
        '`longitude` between ? and ? and `latitude` between ? and ?',
        lng - LNG_LAT_INTERVAL,
        lng + LNG_LAT_INTERVAL,
        lat - LNG_LAT_INTERVAL,
        lat + LNG_LAT_INTERVAL,
      )
    end
  }
  scope :filter_spec, ->(number) { where 'max_spec >= ?', number if number.present? }

  before_save :add_default_options

  def add_default_options
    self.uid = UUIDTools::UUID.random_create.to_s if uid.blank?
    self.status = 'enabled' if status.blank?
  end

  class << self
    # def create(params)
    #   super default_create_options.merge(params)
    # end

    def create!(params)
      transaction do
        super params
        fail ActiveRecord::Rollback
      end
    end

    # params = {
    #   name: '家家',           # option
    #
    #   # 地址
    #   'province' => '广东省',
    #   'city' => '深圳市',
    #   'district' => '南山区',
    #
    #   # datetime
    #   'date' => '2019-05-31',         # require
    #   'start_time' => 12.hours.to_i,  # require
    #   'end_time' => 13.hours.to_i,    # require
    #
    #   # 经纬度
    #   longitude: 123.123456,  # require
    #   latitude: 34.123789,    # require
    #
    #   number: 3               # number of people
    # }
    #
    # return [restaurant1, restaurant2, ...]
    def search(params)
      restaurants = enabled.filter_name(params['name'])
        .filter_province(params['province'])
        .filter_city(params['city'])
        .filter_district(params['district'])
        .filter_address(params['address'])
        .filter_gis(params['longitude'], params['latitude'])
        .filter_spec(params['number'])
        .select(:id, :city, :district, :max_spec)

      result = restaurants.reduce([]) do |temp, restaurant|
        if restaurant.working?(params) && (usable_size = restaurant.usable_tables(params)) > 0
          temp << restaurant.as_json.merge!(usable_size: usable_size)
        end
        temp
      end

      {
        err_code: 0,
        err_msg: 'success',
        restaurants: result.as_json
      }
    rescue => e
      logger.error e
      {
        err_code: 1,
        err_msg: 'failed'
      }
    end

    # def default_create_options
    #   default_options = {
    #     uid: UUIDTools::UUID.random_create.to_s,
    #     province: 'guangdong',
    #     city: 'shenzhen',
    #     district: 'nanshan',
    #     address: 'nantou disi gongyequ 7A 805',
    #     longitude: 123.123456,
    #     latitude: 34.123789,
    #     status: 'enabled',
    #     max_spec: 0
    #   }
    # end

    # params = {
    #   restaurant_id: 1,
    #   name: 'john',
    #   mobile: '13112345678',
    #   date: '2019-06-01',
    #   start_time: 36000,
    #   end_time: 42000,
    #   number: 3
    # }
    #
    # return reservation or nil
    def add_reservation(params)
      restaurant = find params['restaurant_id']
      unless restaurant.working?(params.slice('date', 'start_time', 'end_time'))
        return {err_code: 1, err_msg: 'Non-working times'}
      end
      result = {err_code: 0, err_msg: 'success'}
      transaction do
        reservation = restaurant.reservations.create!(params)

        # before used tables
        before_tables = restaurant.reservations
          .where('id <= ?', reservation.id)
          .filter_date(params['date'])
          .filter_time(params['start_time'], params['end_time'])
          .order(:number)
          .pluck(:number)
          .reduce(restaurant.all_tables) do |temp, number|
            disabled = true
            number.upto(restaurant.max_spec) do |spec|
              if temp[spec] > 0
                temp[spec], disabled = temp[spec].pred, false
                break
              end
            end

            if disabled
              result = {err_code: 1, err_msg: 'Excess budget'}
              fail ActiveRecord::Rollback
            end
            temp
          end
      end
      result
    rescue => e
      logger.error e
      {err_code: 1, err_msg: e.message}
    end
  end

  # all tables
  # eg
  #   [2]*2 + [4]*3 + [8]*2
  #   [0, 0, 2, 0, 3, 0, 0, 0, 2]
  def all_tables
    dining_tables   #.filter_spec(params['number'])
      .select(:spec, :number)
      .as_json
      .reduce([0] * max_spec.next) do |temp, table|
        temp[table['spec']] += table['number']
        temp
      end
  end

  # eg
  # params = {
  #   date: '2019-05-01',
  #   start_time: 36000,
  #   end_time: 42000
  # }
  # return [2,3,4,4]
  def used_tables(params)
    reservations
      .filter_date(params['date'])
      .filter_time(params['start_time'], params['end_time'])
      .order(:number)
      .pluck(:number)
  end

  def usable_tables(params)
    # byebug if id.eql? 32
    tables = used_tables(params)
      .reduce(all_tables) do |temp, number|
        disabled = true
        number.upto(max_spec) do |spec|
          if temp[spec] > 0
            temp[spec], disabled = temp[spec].pred, false
            break
          end
        end

        return 0 if disabled
        temp
      end
    tables[params['number'].to_i..-1].sum
  end

  # params = {
  #   date: '2019-05-01',
  #   start_time: '36000',
  #   end_time: '42000',
  # }
  # return true
  def working?(params)
    work_times.filter_open.any?{|time| time.validate_rule?(params)} && \
      work_times.filter_closed.none?{|time| time.validate_rule?(params)}
  end
end
